package stat;

public class Matrix {

	private double value[][];

	private int cols;

	private int rows;

	public Matrix(int r, int c) {
		initValues(r, c);
	}

	public void initValues(int r, int c) {
		rows = r;
		cols = c;
		value = new double[rows][];
		for (int i = 0; i < value.length; i++) {
			value[i] = new double[cols];
		}
	}

	public double valueAt(int r, int c) {
		return value[r][c];
	}

	public void setValueAt(int r, int c, double v) {
		value[r][c] = v;
	}

	public int numberOfRows() {
		return rows;
	}

	public int numberOfCols() {
		return cols;
	}

	public double[] getRowOf(int i) {
		double[] is = new double[numberOfCols()];
		for (int ii = 0; ii < is.length; ii++) {
			is[ii] = valueAt(i, ii);
		}
		return is;
	}

	public double[] getColOf(int i) {
		double[] is = new double[numberOfRows()];
		for (int ii = 0; ii < is.length; ii++) {
			is[ii] = valueAt(ii, i);
		}
		return is;
	}

	public Matrix getSubMatrix(int startX, int startY, int endX, int engY) {
		int dx = endX - startX + 1, dy = engY - startY + 1;
		Matrix m = new Matrix(dy, dx);

		for (int i = 0; i < dy; i++) {
			for (int j = 0; j < dx; j++) {
				m.setValueAt(i, j, valueAt(i+startY, j+startX));
			}
		}

		return m;
	}

	public Matrix add(Matrix mate) {
		if (numberOfCols() != mate.numberOfCols() || numberOfRows() != mate.numberOfRows()) {
			System.err.println("matrix add imcompactible");
			return null;
		}

		Matrix m = new Matrix(numberOfRows(), numberOfCols());
		for (int i = 0; i < numberOfRows(); i++) {
			for (int j = 0; j < numberOfCols(); j++) {
				m.setValueAt(i, j, valueAt(i, j) + mate.valueAt(i, j));
			}
		}
		return m;
	}

	public Matrix sutract(Matrix mate) {
		if (numberOfCols() != mate.numberOfCols() || numberOfRows() != mate.numberOfRows()) {
			System.err.println("matrix add imcompactible");
			return null;
		}

		Matrix m = new Matrix(numberOfRows(), numberOfCols());
		for (int i = 0; i < numberOfRows(); i++) {
			for (int j = 0; j < numberOfCols(); j++) {
				m.setValueAt(i, j, valueAt(i, j) - mate.valueAt(i, j));
			}
		}
		return m;
	}

	public Matrix multiply(Matrix mate) {
		if (numberOfCols() != mate.numberOfRows()) {
			System.err.println("matrix multiply imcompactible");
			return null;
		}

		Matrix m = new Matrix(numberOfRows(), mate.numberOfCols());
		for (int i = 0; i < m.numberOfRows(); i++) {
			for (int j = 0; j < m.numberOfCols(); j++) {
				double v = 0;
				for (int k = 0; k < numberOfCols(); k++) {
					v += valueAt(i, k) * mate.valueAt(k, j);
				}
				m.setValueAt(i, j, v);
			}
		}
		return m;
	}

	public Matrix inverse() {
		double det = det();
		if (det == 0 || (numberOfRows() != numberOfCols())) {
			System.err.println("matrix inverse not exists");
			return null;
		}

		Matrix m = new Matrix(numberOfRows(), numberOfCols());
		for (int i = 0; i < numberOfRows(); i++) {
			for (int j = 0; j < numberOfCols(); j++) {
				int sign = (((i + j) % 2) == 0) ? 1 : -1;
				m.setValueAt(j, i, sign * beta(i, j).det() / det);
			}
		}
		return m;
	}

	public Matrix transpose() {
		Matrix m = new Matrix(numberOfCols(), numberOfRows());
		for (int i = 0; i < numberOfRows(); i++) {
			for (int j = 0; j < numberOfCols(); j++) {
				m.setValueAt(j, i, valueAt(i, j));
			}
		}
		return m;
	}

	public Matrix scale(double s) {
		Matrix m = new Matrix(numberOfRows(), numberOfCols());
		for (int i = 0; i < numberOfRows(); i++) {
			for (int j = 0; j < numberOfCols(); j++) {
				m.setValueAt(i, j, valueAt(i, j)*s);
			}
		}
		return m;
	}

	public double det() {
		if (numberOfCols() != numberOfRows() || numberOfCols() == 0 || numberOfRows() == 0) {
			System.err.println("matrix det not applicable");
			return 0;
		}

		double v = 0;

		if (numberOfCols() == 1) {
			return valueAt(0, 0);
		} else {
			// row - 0
			for (int i = 0; i < numberOfCols(); i++) {
				int sign = ((i % 2) == 0) ? 1 : -1;
				v += sign * valueAt(0, i) * beta(0, i).det();
//				System.err.println(v);
			}

		}

		return v;
	}

	public Matrix rowOf(int i) {
		if (i < 0 || i >= numberOfRows()) {
			System.err.println("matrix rowOf out of index");
			return null;
		}

		Matrix m = new Matrix(1, numberOfCols());
		for (int j = 0; j < numberOfCols(); j++) {
			m.setValueAt(0, j, valueAt(i, j));
		}
		return m;
	}

	public Matrix colOf(int i) {
		if (i < 0 || i >= numberOfCols()) {
			System.err.println("matrix colOf out of index");
			return null;
		}

		Matrix m = new Matrix(numberOfRows(), 1);
		for (int j = 0; j < numberOfRows(); j++) {
			m.setValueAt(j, 0, valueAt(j, i));
		}
		return m;
	}

	public Matrix beta(int r, int c) {
		// delete r row and c col
		int iRow = -1, iCol = -1;
		Matrix m = new Matrix(numberOfRows() - 1, numberOfCols() - 1);
		for (int i = 0; i < numberOfRows(); i++) {
			if (i == r) continue;
			if (i > r) {
				iRow = i - 1;
			} else {
				iRow = i;
			}
			for (int j = 0; j < numberOfCols(); j++) {
				if (j == c) continue;
				if (j > c) {
					iCol = j - 1;
				} else {
					iCol = j;
				}

				m.setValueAt(iRow, iCol, valueAt(i, j));
			}
		}
//		m.print();
		return m;
	}

	public void print() {
		System.out.print("+-");
		for (int i = 0; i < numberOfCols(); i++) {
			System.out.print("--------------");
		}
		System.out.print("+\n");

		for (int i = 0; i < numberOfRows(); i++) {
			System.out.print("  ");
			for (int j = 0; j < numberOfCols(); j++) {
				System.out.printf("%10.10f ", valueAt(i, j));
			}
			System.out.print(" \n");
		}

		System.out.print("+-");
		for (int i = 0; i < numberOfCols(); i++) {
			System.out.print("--------------");
		}
		System.out.print("+\n");
	}

	public void setValues(double[][] v) {
		value = v;
	}

	public static void main(String[] args) {
//		Matrix m1 = new Matrix(2,3);
//		Matrix m2 = new Matrix(2,3);
//		Matrix m3 = new Matrix(3,1);
//
//		m1.setValueAt(1, 2, 5);
//		m1.setValueAt(0, 2, 5);
//
//		m2.setValueAt(0, 1, 3);
//		m2.setValueAt(0, 0, 2);
//
//		m3.setValueAt(0, 0, 1);
//		m3.setValueAt(1, 0, 2);
//		m3.setValueAt(2, 0, 5);
//
//		m1.print();
//		m2.print();
//
//		m1 = m1.add(m2);
//		m1.print();
//
//		m2 = m2.sutract(m1);
//		m2.print();
//
//		m1 = m1.multiply(m3);
//		m1.print();
		Matrix m = new Matrix(4,4);
		m.setValues(new double[][] {
				new double[] { 1, 2, 3, 1 },
				new double[] { 2, 4, 1, 2 },
				new double[] { 0, 1, 1, 2 },
				new double[] { 1, 2, 3, 5 }
		});
		System.out.println(m.det());

		m.inverse().multiply(m).print();
//		m.setValueAt(0, 0, 1);
//		m.setValueAt(0, 1, 2);
//		m.setValueAt(0, 2, 5);
//		m.setValueAt(1, 1, 2);
//		m.setValueAt(2, 2, 2);
//		m.setValueAt(2, 0, 1);
//		m.print();
//		System.out.println(m.det());
//
//		m.scale(2).print();
//
//		m.inverse().print();
//		m.transpose().print();
//
//		Matrix mm = m.multiply(m.inverse());
//
//		mm.rowOf(2).print();
//		mm.colOf(1).print();
	}
}
